package com.tanjie.blackbean.system.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.tanjie.blackbean.cache.repositories.UserInfoCache;
import com.tanjie.blackbean.cache.repositories.UserInfoCacheRepository;
import com.tanjie.blackbean.core.util.PasswordUtil;
import com.tanjie.blackbean.mybatis.query.Condition;
import com.tanjie.blackbean.mybatis.query.PageParam;
import com.tanjie.blackbean.mybatis.support.PageConverter;
import com.tanjie.blackbean.pojo.auth.UserInfo;
import com.tanjie.blackbean.pojo.auth.UserInfoWithRolesAndPerms;
import com.tanjie.blackbean.pojo.properties.BlackbeanAuthProperties;
import com.tanjie.blackbean.pojo.system.ClientType;
import com.tanjie.blackbean.pojo.system.UserInfoQuery;
import com.tanjie.blackbean.pojo.system.UserType;
import com.tanjie.blackbean.system.admin.dao.*;
import com.tanjie.blackbean.system.admin.model.org.OrgPO;
import com.tanjie.blackbean.system.admin.model.permission.PermissionPO;
import com.tanjie.blackbean.system.admin.model.rel.RolePermissionPO;
import com.tanjie.blackbean.system.admin.model.rel.UserRolePO;
import com.tanjie.blackbean.system.admin.model.role.RolePO;
import com.tanjie.blackbean.system.admin.model.user.*;
import com.tanjie.blackbean.system.admin.service.IOrgService;
import com.tanjie.blackbean.system.admin.service.IUserService;
import com.tanjie.blackbean.pojo.exception.AuthException;
import com.tanjie.blackbean.pojo.exception.BizException;
import com.tanjie.blackbean.pojo.response.AuthErrorCode;
import com.tanjie.blackbean.pojo.response.BizErrorCode;
import com.tanjie.blackbean.core.web.BlackbeanRequestContextHolder;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户表
 *
 * @author tanjie
 * @since 2022-04-20
 */
@Slf4j
@Service
@AllArgsConstructor
public class UserServiceImpl implements IUserService {

    BlackbeanAuthProperties authProperties;

    UserMapper userMapper;
    UserDaoService userDao;

    OrgMapper orgMapper;
    IOrgService orgService;

    RoleMapper roleMapper;
    RoleMenuMapper roleMenuMapper;
    RolePermissionMapper rolePermissionMapper;

    UserRoleMapper userRoleMapper;
    PermissionMapper permissionMapper;

    UserInfoCacheRepository userInfoCacheRepository;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserVO create(UserDTO dto) {
        verifyUsernameRepeat(dto.getUsername(), null);
        orgService.checkOrgExist(dto.getOrgId());
        UserPO newUser = BeanUtil.copyProperties(dto, UserPO.class);
        String password = dto.getPassword();
        if (StrUtil.isBlank(password)) {
            newUser.setPassword(PasswordUtil.encryptPassword(authProperties.getDefaultPassword()));
        } else {
            newUser.setPassword(PasswordUtil.encryptPassword(password));
        }
        userDao.save(newUser);
        bindRole(newUser.getId(), dto.getRoleIds());
        return BeanUtil.copyProperties(newUser, UserVO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(UserDTO dto) {
        String id = dto.getId();
        if (StrUtil.isBlank(id)) {
            throw new BizException(BizErrorCode.ID_REQUIRE_NOT_BLANK);
        }
        UserVO existOne = getByIdThr(id);
        // 校验用户名称
        String modifyUsername = dto.getUsername();
        String existOneUsername = existOne.getUsername();
        if (!existOneUsername.equals(modifyUsername)) {
            verifyUsernameRepeat(modifyUsername, id);
        }
        String existOneOrgId = existOne.getOrgId();
        String modifyOrgId = dto.getOrgId();
        if (!existOneOrgId.equals(modifyOrgId)) {
            orgService.checkOrgExist(modifyOrgId);
        }
        UserPO updateOne = BeanUtil.copyProperties(dto, UserPO.class);
        updateOne.setPassword(null);
        userDao.updateById(updateOne);
        // 重新绑定角色
        bindRole(id, dto.getRoleIds());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String id) {
        if (StrUtil.isBlank(id)) {
            throw new BizException(BizErrorCode.ID_REQUIRE_NOT_BLANK);
        }
        UserPO existOne = userMapper.selectById(id);
        if (existOne == null) {
            throw new BizException(BizErrorCode.ID_NOT_EXIST);
        }
        userMapper.deleteById(id);
        userRoleMapper.delete(new LambdaQueryWrapper<UserRolePO>().eq(UserRolePO::getUserId, id));
    }

    @Override
    public UserVO getByIdThr(String id) {
        if (StrUtil.isBlank(id)) {
            throw new BizException(BizErrorCode.ID_REQUIRE_NOT_BLANK);
        }
        UserPO existOne = userMapper.selectById(id);
        if (existOne == null) {
            throw new BizException(BizErrorCode.ID_NOT_EXIST);
        }
        return BeanUtil.copyProperties(existOne, UserVO.class);
    }

    @Override
    public void checkIdExist(String id) {
        if (new LambdaQueryChainWrapper<>(userMapper).eq(UserPO::getId, id).count() == 0) {
            throw new BizException("用户id:{}在系统中不存在", id);
        }
    }

    @Override
    public UserVO detail(String id) {
        UserVO vo = getByIdThr(id);
        //  用户绑定角色id数组
        List<String> roleIds = new LambdaQueryChainWrapper<>(userRoleMapper)
                .eq(UserRolePO::getUserId, id)
                .list()
                .stream()
                .map(UserRolePO::getRoleId)
                .distinct()
                .collect(Collectors.toList());
        vo.setRoleIds(roleIds);
        return vo;
    }

    @Override
    public UserVO getByUsername(String username) {
        return Optional.ofNullable(new LambdaQueryChainWrapper<>(userMapper).eq(UserPO::getUsername, username).one())
                .map(userPO -> BeanUtil.copyProperties(userPO, UserVO.class)).orElse(null);
    }

    @Override
    public List<UserVO> queryList(UserQuery dto) {
        LambdaQueryWrapper<UserPO> queryWrapper = getQueryWrapper(dto);
        List<UserPO> list = userMapper.selectList(queryWrapper);
        return BeanUtil.copyToList(list, UserVO.class);
    }

    @Override
    public IPage<UserVO> queryPage(UserQuery dto, PageParam pageParam) {
        IPage<UserPO> page = Condition.getPage(pageParam);
        LambdaQueryWrapper<UserPO> queryWrapper = getQueryWrapper(dto);
        IPage<UserPO> userPoPage = userMapper.selectPage(page, queryWrapper);
        List<UserPO> poList = userPoPage.getRecords();
        Map<String, String> orgNameMap = new HashMap<>();
        if (CollectionUtil.isNotEmpty(poList)) {
            orgNameMap = orgMapper.selectBatchIds(poList.stream().map(UserPO::getOrgId).collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(OrgPO::getId, OrgPO::getName));
        }
        Map<String, String> finalOrgNameMap = orgNameMap;
        return PageConverter.convert(userPoPage,
                po -> {
                    UserVO vo = BeanUtil.copyProperties(po, UserVO.class);
                    vo.setOrgName(finalOrgNameMap.get(po.getOrgId()));
                    return vo;
                });
    }

    @Override
    public UserInfoWithRolesAndPerms queryUserInfo(UserInfoQuery loginParam) {
        ClientType clientType = loginParam.getClientType();
        UserType userType = clientType.equals(ClientType.SYS) ? UserType.SYS : UserType.OPT;
        Boolean encrypted = loginParam.getEncrypted();
        String pwd = loginParam.getPassword();
        if (!encrypted) {
            pwd = PasswordUtil.encryptPassword(pwd);
        }
        String username = loginParam.getUsername();
        UserPO targetUser = new LambdaQueryChainWrapper<>(userMapper)
                .eq(UserPO::getUsername, username)
                .eq(UserPO::getPassword, pwd)
                .eq(UserPO::getUserType, userType.getCode())
                .one();
        if (targetUser == null) {
            return null;
        }
        UserInfoWithRolesAndPerms userInfo = BeanUtil.copyProperties(targetUser, UserInfoWithRolesAndPerms.class);
        // 设置组织机构的信息
        Optional.ofNullable(orgMapper.selectById(targetUser.getOrgId())).ifPresent(o -> {
            userInfo.setOrgName(o.getName());
            userInfo.setOrgFullName(o.getFullName());
            userInfo.setOrgLogo("");
        });
        userInfo.setClientType(clientType);
        userInfo.setUserType(userType);
        attachWithRolesAndPerms(userInfo, clientType);
        return userInfo;
    }

    @Override
    public MeVO getCurrentUserInfo() {
        UserInfoCache userInfoCache = userInfoCacheRepository.findById(BlackbeanRequestContextHolder.getUserIdThr()).orElse(null);
        if (userInfoCache == null) {
            throw new AuthException(AuthErrorCode.CANNOT_GET_USERINFO_FROM_CACHE);
        }
        List<UserInfo.Role> roleList = userInfoCache.getRoles();
        List<UserInfo.Permission> permissionList = userInfoCache.getPermissions();
        MeVO me = BeanUtil.copyProperties(userInfoCache, MeVO.class);
        me.setRoles(roleList.stream().map(UserInfo.Role::getCode).collect(Collectors.toList()));
        me.setPerms(permissionList.stream().map(UserInfo.Permission::getBtnPerm).collect(Collectors.toList()));
        return me;
    }

    // =================================================================================================================

    private LambdaQueryWrapper<UserPO> getQueryWrapper(UserQuery dto) {
        LambdaQueryWrapper<UserPO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(UserPO::getUpdateTime);
        if (dto != null) {
            String orgId = dto.getOrgId();
            Integer status = dto.getStatus();
            String username = dto.getUsername();
            Integer userType = dto.getUserType();
            queryWrapper.eq(StrUtil.isNotBlank(orgId), UserPO::getOrgId, orgId)
                    .eq(status != null, UserPO::getStatus, status)
                    .like(StrUtil.isNotBlank(username), UserPO::getUsername, username)
                    .eq(userType != null, UserPO::getUserType, userType);
        }
        return queryWrapper;
    }

    private void verifyUsernameRepeat(String username, String existId) {
        if (new LambdaQueryChainWrapper<>(userMapper)
                .eq(UserPO::getUsername, username)
                .ne(StrUtil.isNotBlank(existId), UserPO::getId, existId)
                .count() > 0) {
            throw new BizException("用户名:{}在系统中已存在", username);
        }
    }

    private void bindRole(String userId, List<String> roleIds) {
        userRoleMapper.delete(new LambdaQueryWrapper<UserRolePO>().eq(UserRolePO::getUserId, userId));
        if (CollectionUtil.isNotEmpty(roleIds)) {
            List<String> notExistRoleIdList = new ArrayList<>();
            for (String roleId : roleIds) {
                if (new LambdaQueryChainWrapper<>(roleMapper).eq(RolePO::getId, roleId).count() == 0) {
                    notExistRoleIdList.add(roleId);
                }
            }
            if (CollectionUtil.isNotEmpty(notExistRoleIdList)) {
                throw new BizException("这些角色id:{}在系统中不存在", notExistRoleIdList);
            }
            roleIds.forEach(roleId -> userRoleMapper.insert(new UserRolePO(userId, roleId)));
        }
    }

    private void attachWithRolesAndPerms(UserInfoWithRolesAndPerms userInfo, ClientType clientType) {
        // 查询用户-角色关联表 得到当前用户绑定的所有角色id
        List<String> roleIdList = new LambdaQueryChainWrapper<>(userRoleMapper)
                .eq(UserRolePO::getUserId, userInfo.getId())
                .list()
                .stream()
                .map(UserRolePO::getRoleId)
                .collect(Collectors.toList());
        //
        if (CollectionUtil.isNotEmpty(roleIdList)) {
            // 查询角色表
            List<RolePO> roleList = new LambdaQueryChainWrapper<>(roleMapper).in(RolePO::getId, roleIdList).list();
            //
            if (CollectionUtil.isNotEmpty(roleList)) {
                // 确实查询到角色表数据 设置到userInfo对象中
                userInfo.setRoles(BeanUtil.copyToList(roleList, UserInfoWithRolesAndPerms.Role.class));
                // 接着去获取这些角色所拥有的权限节点
                // 且是当前client-type下中的所有权限节点
                List<UserInfoWithRolesAndPerms.Permission> allPermissions = new ArrayList<>();
                for (RolePO role : roleList) {
                    // 查询角色-权限节点的关联表
                    List<String> permissionIdList = new LambdaQueryChainWrapper<>(rolePermissionMapper)
                            .eq(RolePermissionPO::getRoleId, role.getId())
                            .list()
                            .stream()
                            .map(RolePermissionPO::getPermissionId)
                            .distinct() // 关联表没有添加唯一索引、代码也没有判断唯一性、有可能出现重复数据、但是影响不大、在这里去重一下
                            .collect(Collectors.toList());
                    // 如果当前角色有绑定权限节点，则通过权限id数组去查询数据
                    if (CollectionUtil.isNotEmpty(permissionIdList)) {
                        new LambdaQueryChainWrapper<>(permissionMapper)
                                .in(PermissionPO::getId, permissionIdList)
                                .eq(PermissionPO::getClientType, clientType)
                                .list()
                                .forEach(po -> {
                                    UserInfoWithRolesAndPerms.Permission permission = BeanUtil.copyProperties(po, UserInfoWithRolesAndPerms.Permission.class);
                                    // 一个用户可能会绑定相同的菜单和权限，这里做一下判断，避免数据重复
                                    if (!allPermissions.contains(permission)) {
                                        allPermissions.add(permission);
                                    }
                                });
                    }
                }
                userInfo.setPermissions(allPermissions);
            }
        }
    }

}
